using System;
using System.IO;
using System.Reflection;
using System.Collections.Generic;

using Crainiate.Data.Persistence.Providers;

namespace Crainiate.Data.Persistence.Cache
{
	public sealed class ReflectionCacheItem
	{
		//Property variables
		private string _name;
		private string _fullName;
		private bool _isUnderlying;
		private string _underlyingName;
		private Type _type;
		private Type _underlyingType;
		private Type _keyType;
        private Type _persistType;
        private Type _baseType;
        
        private bool _hasObjects;
        private bool _isIdentity;
        private bool _isView;
        private bool _hasPrimaryKey;
        private bool _hasCompoundKeys;
        private bool _hasForeignKeys;
        private bool _hasBinaryProperties;

        private int _rank;

        private PropertyInfo _primaryKey;
        private PropertyInfo _identity;
        private PropertyInfo[] _properties;
        private PropertyInfo[] _foreignKeys;
        private PropertyInfo[] _compoundKeys;
        private PropertyInfo[] _valueProperties;
        private PropertyInfo[] _referenceProperties;
        private PropertyInfo[] _binaryProperties;

        //Working variables
        private Dictionary<PropertyInfo, PropertyInfo> _originalProperties;

		public ReflectionCacheItem(Type type, ReflectionParserBase parser)
		{
			_type = type;

            _name = parser.Name;
		    _fullName = parser.FullName;
		    _isUnderlying = parser.IsUnderlying;
		    _underlyingName = parser.UnderlyingName;
		    _underlyingType = parser.UnderlyingType;
		    _keyType = parser.KeyType;
            _persistType = parser.PersistType;
            _baseType = parser.BaseType;
        
            _hasObjects = parser.HasObjects;
            _isIdentity = parser.IsIdentity;
            _isView = parser.IsView;
            _hasPrimaryKey = parser.HasPrimaryKey;
            _hasCompoundKeys = parser.HasCompoundKeys;
            _hasForeignKeys = parser.HasForeignKeys;
            _hasBinaryProperties = parser.HasBinaryProperties;

            _primaryKey = parser.PrimaryKey;
            _identity = parser.PrimaryKey;
            _properties = parser.Properties;
            _foreignKeys = parser.ForeignKeys;
            _compoundKeys = parser.CompoundKeys;
            _valueProperties = parser.ValueProperties;
            _referenceProperties = parser.ReferenceProperties;
            _binaryProperties = parser.BinaryProperties;

            _rank = parser.Rank;
		}

		public bool IsUnderlying
		{
			get
			{
				return _isUnderlying;
			}
		}

        public bool IsIdentity
        {
            get
            {
                return _isIdentity;
            }
        }

        public bool IsView
        {
            get
            {
                return _isView;
            }
        }

        public bool HasPrimaryKey
        {
            get
            {
                return _hasPrimaryKey;
            }
        }

        public bool HasCompoundKeys
        {
            get
            {
                return _hasCompoundKeys;
            }
        }

        public bool HasForeignKeys
        {
            get
            {
                return _hasForeignKeys;
            }
        }

        public bool HasObjects
        {
            get
            {
                return _hasObjects;
            }
        }
        public bool HasBinaryProperties
        {
            get
            {
                return _hasBinaryProperties;
            }
        }

		public string Name
		{
			get
			{
				return _name;
			}
		}

		public string FullName
		{
			get
			{
				return _fullName;
			}
		}

		public string UnderlyingName
		{
			get
			{
				return _underlyingName;
			}
		}

		public Type ReflectedType
		{
			get
			{
				return _type;
			}
		}

		public Type UnderlyingType
		{
			get
			{
				return _underlyingType;
			}
		}

        public Type BaseType
        {
            get
            {
                return _baseType;
            }
        }

		public Type KeyType
		{
			get
			{
				return _keyType;
			}
		}

        public Type PersistType
        {
            get
            {
                return _persistType;
            }
        }

        public PropertyInfo PrimaryKey
        {
            get
            {
                return _primaryKey;
            }
        }

        public PropertyInfo Identity
        {
            get
            {
                return _identity;
            }
        }

        public PropertyInfo[] Properties
        {
            get
            {
                return _properties;
            }
        }

        public PropertyInfo[] ForeignKeys
        {
            get
            {
                return _foreignKeys;
            }
        }

        public PropertyInfo[] CompoundKeys
        {
            get
            {
                return _compoundKeys;
            }
        }

        public PropertyInfo[] ValueProperties
        {
            get
            {
                return _valueProperties;
            }
        }

        public PropertyInfo[] ReferenceProperties
        {
            get
            {
                return _referenceProperties;
            }
        }

        public PropertyInfo[] BinaryProperties
        {
            get
            {
                return _binaryProperties;
            }
        }

        public int Rank
        {
            get
            {
                return _rank;
            }
        }
        
		//Methods

        //Returns the key properties even if this type is not the key type
        public PropertyInfo[] GetKeyProperties()
        {
            //Return either the primary key, or the compound keys
            if (HasPrimaryKey) return new PropertyInfo[] {PrimaryKey};
            if (HasCompoundKeys) return CompoundKeys;
            return null;
        }

        public PropertyInfo[] GetAllProperties()
        {
            //Loop through each property and determine if it must be added or changed   
            PropertyInfo[] keys = GetKeyProperties();
            PropertyInfo[] props = Properties;
            PropertyInfo[] all = new PropertyInfo[keys.GetLength(0) + props.GetLength(0)];

            keys.CopyTo(all, 0);
            props.CopyTo(all, keys.Length);

            return all;
        }

        public PropertyInfo GetOriginalProperty(PropertyInfo property)
        {
            if (!IsView) throw new ReflectionCacheException("Cannot get original property for property with class type which is not view.");

            //Load original properties
            if (_originalProperties == null) LoadOriginalProperties(property.ReflectedType);

            return _originalProperties[property];
        }

        public PrimaryKeyAttribute GetPrimaryKeyAttribute()
        {
            if (PrimaryKey == null) return null;

            object[] attributes = PrimaryKey.GetCustomAttributes(false);
            foreach (Attribute attr in attributes)
            {
                if (attr is PrimaryKeyAttribute) return attr as PrimaryKeyAttribute;
            }

            return null;
        }

        public IdentityAttribute GetIdentityAttribute()
        {
            if (Identity == null) return null;

            object[] attributes = PrimaryKey.GetCustomAttributes(false);
            foreach (Attribute attr in attributes)
            {
                if (attr is IdentityAttribute) return attr as IdentityAttribute;
            }

            return null;
        }

        public ForeignKeyAttribute GetForeignKeyAttribute(PropertyInfo prop)
        {
            if (prop == null) return null;

            object[] attributes = prop.GetCustomAttributes(false);
            foreach (Attribute attr in attributes)
            {
                if (attr is ForeignKeyAttribute) return attr as ForeignKeyAttribute;
            }

            return null;
        }

        private void LoadOriginalProperties(Type type)
        {
            _originalProperties = new Dictionary<PropertyInfo, PropertyInfo>();

            ReflectionCacheItem cache = Component.Instance.ReflectionCache.Lookup(type);
            LoadOriginalProperties(type, cache.PersistType);
        }

        private void LoadOriginalProperties(Type type, Type underlyingType)
        {
            ReflectionCacheItem cache = Component.Instance.ReflectionCache.Lookup(type);
            ReflectionCacheItem underlyingCache = Component.Instance.ReflectionCache.Lookup(underlyingType);
            
            //Loop though and add any original properties using view property as key
            foreach (PropertyInfo prop in cache.Properties)
            {
                foreach (PropertyInfo prop2 in underlyingCache.Properties)
                {
                    if (prop.Name == prop2.Name)
                    {
                        _originalProperties.Add(prop, prop2);
                        break;
                    }
                }
            }

            //Recurse for any original properties in a base class
            if (!underlyingCache.IsUnderlying) LoadOriginalProperties(type, underlyingCache.BaseType);

            //Load matching key properties
            if (underlyingCache.IsUnderlying)
            {
                foreach (PropertyInfo prop in cache.Properties)
                {
                    foreach (PropertyInfo prop2 in underlyingCache.GetKeyProperties())
                    {
                        if (prop.Name == prop2.Name)
                        {
                            _originalProperties.Add(prop, prop2);
                            break;
                        }
                    }
                }
            }
        }
		
	}
}
